package naga.x.resource.image.support;

import java.io.Closeable;
import java.io.File;
import java.util.Collections;
import java.util.Map;
import java.util.Queue;
import java.util.WeakHashMap;
import java.util.concurrent.LinkedBlockingQueue;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import naga.x.resource.App;
import naga.x.resource.app.AppConfig;
import naga.x.resource.util.CommonUtils;
import naga.x.resource.util.HttpUtils;
import naga.x.resource.util.IOUtils;

@Service
public class ImageSupport implements Runnable, Closeable, InitializingBean {

	@Autowired
	private AppConfig config;
	
	private byte[] maleDefaultIcon, femaleDefaultIcon;
	private Map<String, byte[]> cache = Collections.synchronizedMap(new WeakHashMap<String, byte[]>(64));
	
	private Thread thread;
	private boolean active;
	private Queue<QueueEntry> queue = new LinkedBlockingQueue<QueueEntry>();
	
	public ImageSupport() {
		thread = new Thread(this, "image update thread");
		thread.start();
		App.LOG.info("[IMAGE UPDATE THREAD]STARTED");
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		maleDefaultIcon = IOUtils.readBytes(new File(config.getResPath(), "1.png"));
		femaleDefaultIcon = IOUtils.readBytes(new File(config.getResPath(), "2.png"));
	}

	@Override
	public void run() {
		active = true;
		while (active) {
			QueueEntry e = queue.poll();
			if (e != null) {
				flush(e);
			} else {
				try {
					Thread.sleep(50);
				} catch (InterruptedException e1) {
				}
			}
		}
	}
	
	private void flush(QueueEntry e) {
		// get the new image data
		byte[] data = HttpUtils.getAsBytes(e.url, null);
		if (data == null || data.length == 0) {
			return;
		}
		// replace the cache and the old file
		cache.put(e.id, data);
		IOUtils.write(e.file, data);
	}
	
	@Override
	public void close() {
		active = false;
		try {
			thread.join(2000);
			App.LOG.info("[IMAGE UPDATE THREAD]CLOSED");
		} catch (Exception e) {
			App.LOG.error(e.getMessage(), e);
		}
	}

	public byte[] find(String id) {
		byte[] data = cache.get(id);
		if (data != null && data.length > 0) {
			return data;
		}
		File f = toFile(id, config.getResPath());
		if (f != null) {
			data = IOUtils.readBytes(f);
			if (data != null && data.length > 0) {
				cache.put(id, data);
				return data;
			}
		}
		return CommonUtils.randomBoolean() ? maleDefaultIcon : femaleDefaultIcon;
	}
	
	public String update(String channel, String url) {
		if (!config.getIpWhiteList().contains(HttpUtils.getRequestAddr())) {
			return null;
		}
		url = CommonUtils.decodeURL(url, IOUtils.UTF8);
		String id = mappingId(channel, url);
		File f = toFile(id, config.getResPath());
		if (!f.exists()) {
			queue.offer(new QueueEntry(id, f, url));
		} else if (f.lastModified() + 3600000 < System.currentTimeMillis()) { // 1小时有效期
			f.setLastModified(System.currentTimeMillis());
			queue.offer(new QueueEntry(id, f, url));
		}
		return id;
	}
	
	private static String mappingId(String channel, String url) {
		if ("qq".equals(channel) || "qq_wanba".equals(channel)) {
			int n = url.indexOf("qlogo.cn/qzopenapp/");
			if (n != -1) {
				url = url.substring(n + "qlogo.cn/qzopenapp/".length());
			}
		}
		return CommonUtils.md5(url) + '_' + channel;
	}
	
	private static File toFile(String id, String resPath) {
		if (id.length() < 32) { // md5.length
			return null;
		}
		int i = id2int(id) & 0xf; // id % 16 (0~15)
		return new File(resPath, i + "/" + id);
	}
	
	private static int id2int(String id) {
		// ffffffff
		int shift = 28;
		int n = 0;
		for (int i = 0; i < 8; i++) {
			char cc = id.charAt(i);
			int c;
			if (cc >= '0' && cc <= '9') {
				c = (cc - '0') << shift;
			} else {
				c = (cc - 'a' + 10) << shift;
			}
			n |= c;
			shift -= 4;
		}
		return n < 0 ? -n : n;
	}
	
	static class QueueEntry {
		String id;
		File file;
		String url;
		QueueEntry(String id, File file, String url) {
			this.id = id;
			this.file = file;
			this.url = url;
		}
	}
	
}
